#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Pytest配置文件

提供全局测试夹具、配置和工具函数
包括：
- 数据库测试夹具
- 异步测试支持
- 模拟对象
- 测试数据生成
- 性能测试工具
- 日志配置
"""

import pytest
import asyncio
import tempfile
import shutil
import os
import sys
import logging
import sqlite3
from unittest.mock import MagicMock, AsyncMock
from typing import Generator, AsyncGenerator, Dict, Any, List
try:
    import pandas as pd
except ImportError:
    pd = None

try:
    import numpy as np
except ImportError:
    np = None

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.dirname(__file__))


# ============================================================================
# 测试会话配置
# ============================================================================

def pytest_configure(config):
    """pytest配置钩子"""
    # 配置日志
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler('test.log'),
            logging.StreamHandler()
        ]
    )
    
    # 设置测试环境变量
    os.environ['TESTING'] = 'true'
    os.environ['PYTHONPATH'] = os.path.dirname(__file__)
    
    # 禁用一些警告
    import warnings
    warnings.filterwarnings('ignore', category=DeprecationWarning)
    warnings.filterwarnings('ignore', category=PendingDeprecationWarning)
    warnings.filterwarnings('ignore', category=UserWarning)


def pytest_unconfigure(config):
    """pytest清理钩子"""
    # 清理环境变量
    os.environ.pop('TESTING', None)
    
    # 清理临时文件
    temp_files = ['test.log', 'test.db', 'test.sqlite']
    for temp_file in temp_files:
        if os.path.exists(temp_file):
            try:
                os.remove(temp_file)
            except Exception:
                pass


def pytest_collection_modifyitems(config, items):
    """修改测试收集项"""
    # 为异步测试添加asyncio标记
    for item in items:
        if asyncio.iscoroutinefunction(item.function):
            item.add_marker(pytest.mark.asyncio)
        
        # 根据测试名称添加标记
        if 'slow' in item.name or 'performance' in item.name:
            item.add_marker(pytest.mark.slow)
        elif 'fast' in item.name or 'unit' in item.name:
            item.add_marker(pytest.mark.fast)
        
        if 'database' in item.name or 'db' in item.name:
            item.add_marker(pytest.mark.database)
        
        if 'web' in item.name or 'api' in item.name:
            item.add_marker(pytest.mark.web)
        
        if 'ml' in item.name or 'machine_learning' in item.name:
            item.add_marker(pytest.mark.ml)


# ============================================================================
# 基础夹具
# ============================================================================

@pytest.fixture(scope="session")
def event_loop():
    """创建事件循环"""
    loop = asyncio.new_event_loop()
    yield loop
    loop.close()


@pytest.fixture(scope="function")
def temp_dir() -> Generator[str, None, None]:
    """创建临时目录"""
    temp_path = tempfile.mkdtemp()
    yield temp_path
    shutil.rmtree(temp_path, ignore_errors=True)


@pytest.fixture(scope="function")
def temp_file() -> Generator[str, None, None]:
    """创建临时文件"""
    fd, temp_path = tempfile.mkstemp()
    os.close(fd)
    yield temp_path
    try:
        os.unlink(temp_path)
    except FileNotFoundError:
        pass


@pytest.fixture(scope="function")
def logger():
    """测试日志器"""
    logger = logging.getLogger('test')
    logger.setLevel(logging.DEBUG)
    return logger


# ============================================================================
# 数据库夹具
# ============================================================================

@pytest.fixture(scope="function")
def sqlite_db() -> Generator[sqlite3.Connection, None, None]:
    """SQLite内存数据库连接"""
    conn = sqlite3.connect(':memory:')
    conn.row_factory = sqlite3.Row
    yield conn
    conn.close()


@pytest.fixture(scope="function")
def sqlite_file_db(temp_file) -> Generator[sqlite3.Connection, None, None]:
    """SQLite文件数据库连接"""
    conn = sqlite3.connect(temp_file)
    conn.row_factory = sqlite3.Row
    yield conn
    conn.close()


@pytest.fixture(scope="function")
def mock_database():
    """模拟数据库连接"""
    mock_db = MagicMock()
    mock_db.execute.return_value = MagicMock()
    mock_db.fetchall.return_value = []
    mock_db.fetchone.return_value = None
    mock_db.commit.return_value = None
    mock_db.rollback.return_value = None
    return mock_db


@pytest.fixture(scope="function")
async def async_mock_database():
    """异步模拟数据库连接"""
    mock_db = AsyncMock()
    mock_db.execute.return_value = AsyncMock()
    mock_db.fetchall.return_value = []
    mock_db.fetchone.return_value = None
    mock_db.commit.return_value = None
    mock_db.rollback.return_value = None
    return mock_db


# ============================================================================
# 数据生成夹具
# ============================================================================

@pytest.fixture
def sample_dataframe():
    """创建示例DataFrame用于测试"""
    if pd is None:
        pytest.skip("pandas not available")
    data = {
        'name': ['Alice', 'Bob', 'Charlie', 'David'],
        'age': [25, 30, 35, 28],
        'city': ['New York', 'London', 'Tokyo', 'Paris'],
        'salary': [50000, 60000, 70000, 55000]
    }
    return pd.DataFrame(data)


@pytest.fixture(scope="function")
def sample_numpy_array():
    """示例NumPy数组"""
    if np is None:
        pytest.skip("numpy not available")
    np.random.seed(42)
    return np.random.randn(100, 10)


@pytest.fixture(scope="function")
def sample_user_data():
    """示例用户数据"""
    return {
        'id': 1,
        'name': 'Test User',
        'email': 'test@example.com',
        'age': 25,
        'department': 'IT',
        'salary': 50000,
        'active': True,
        'created_at': '2023-01-01T00:00:00Z'
    }


@pytest.fixture(scope="function")
def sample_users_list():
    """示例用户列表"""
    return [
        {'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'age': 25},
        {'id': 2, 'name': 'Bob', 'email': 'bob@example.com', 'age': 30},
        {'id': 3, 'name': 'Charlie', 'email': 'charlie@example.com', 'age': 35},
        {'id': 4, 'name': 'Diana', 'email': 'diana@example.com', 'age': 28},
        {'id': 5, 'name': 'Eve', 'email': 'eve@example.com', 'age': 32}
    ]


@pytest.fixture(scope="function")
def sample_csv_data():
    """示例CSV数据"""
    return """id,name,age,city
1,Alice,25,New York
2,Bob,30,London
3,Charlie,35,Tokyo
4,Diana,28,Paris
5,Eve,32,Sydney"""


@pytest.fixture(scope="function")
def sample_json_data():
    """示例JSON数据"""
    return {
        "users": [
            {"id": 1, "name": "Alice", "age": 25, "city": "New York"},
            {"id": 2, "name": "Bob", "age": 30, "city": "London"},
            {"id": 3, "name": "Charlie", "age": 35, "city": "Tokyo"}
        ],
        "metadata": {
            "total": 3,
            "created_at": "2023-01-01T00:00:00Z"
        }
    }


# ============================================================================
# Web测试夹具
# ============================================================================

@pytest.fixture(scope="function")
def mock_fastapi_app():
    """模拟FastAPI应用"""
    from fastapi import FastAPI
    app = FastAPI(title="Test API", version="1.0.0")
    
    @app.get("/")
    async def root():
        return {"message": "Hello Test"}
    
    @app.get("/health")
    async def health():
        return {"status": "healthy"}
    
    @app.get("/users/{user_id}")
    async def get_user(user_id: int):
        return {"id": user_id, "name": f"User {user_id}"}
    
    return app


@pytest.fixture(scope="function")
def mock_http_client():
    """模拟HTTP客户端"""
    client = MagicMock()
    client.get.return_value.status_code = 200
    client.get.return_value.json.return_value = {"message": "success"}
    client.post.return_value.status_code = 201
    client.post.return_value.json.return_value = {"id": 1, "created": True}
    client.put.return_value.status_code = 200
    client.put.return_value.json.return_value = {"updated": True}
    client.delete.return_value.status_code = 204
    return client


@pytest.fixture(scope="function")
async def async_mock_http_client():
    """异步模拟HTTP客户端"""
    client = AsyncMock()
    client.get.return_value.status_code = 200
    client.get.return_value.json.return_value = {"message": "success"}
    client.post.return_value.status_code = 201
    client.post.return_value.json.return_value = {"id": 1, "created": True}
    return client


# ============================================================================
# 性能测试夹具
# ============================================================================

@pytest.fixture(scope="function")
def performance_timer():
    """性能计时器"""
    import time
    
    class Timer:
        def __init__(self):
            self.start_time = None
            self.end_time = None
        
        def start(self):
            self.start_time = time.time()
        
        def stop(self):
            self.end_time = time.time()
        
        @property
        def elapsed(self):
            if self.start_time and self.end_time:
                return self.end_time - self.start_time
            return None
        
        def __enter__(self):
            self.start()
            return self
        
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.stop()
    
    return Timer()


@pytest.fixture(scope="function")
def memory_profiler():
    """内存分析器"""
    import psutil
    import os
    
    class MemoryProfiler:
        def __init__(self):
            self.process = psutil.Process(os.getpid())
            self.start_memory = None
            self.end_memory = None
        
        def start(self):
            self.start_memory = self.process.memory_info().rss
        
        def stop(self):
            self.end_memory = self.process.memory_info().rss
        
        @property
        def memory_used(self):
            if self.start_memory and self.end_memory:
                return self.end_memory - self.start_memory
            return None
        
        def __enter__(self):
            self.start()
            return self
        
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.stop()
    
    try:
        return MemoryProfiler()
    except ImportError:
        # 如果psutil不可用，返回模拟对象
        mock_profiler = MagicMock()
        mock_profiler.memory_used = 0
        return mock_profiler


# ============================================================================
# 异步测试夹具
# ============================================================================

@pytest.fixture(scope="function")
async def async_context():
    """异步上下文"""
    context = {
        'tasks': [],
        'connections': [],
        'resources': []
    }
    
    yield context
    
    # 清理异步资源
    for task in context['tasks']:
        if not task.done():
            task.cancel()
            try:
                await task
            except asyncio.CancelledError:
                pass
    
    for connection in context['connections']:
        if hasattr(connection, 'close'):
            await connection.close()
    
    for resource in context['resources']:
        if hasattr(resource, 'cleanup'):
            await resource.cleanup()


@pytest.fixture(scope="function")
def async_mock_factory():
    """异步模拟对象工厂"""
    def create_async_mock(**kwargs):
        mock = AsyncMock(**kwargs)
        return mock
    
    return create_async_mock


# ============================================================================
# 测试工具函数
# ============================================================================

@pytest.fixture(scope="session")
def test_utils():
    """测试工具函数集合"""
    class TestUtils:
        @staticmethod
        def assert_dict_contains(actual: Dict[str, Any], expected: Dict[str, Any]):
            """断言字典包含预期的键值对"""
            for key, value in expected.items():
                assert key in actual, f"Key '{key}' not found in actual dict"
                assert actual[key] == value, f"Value for key '{key}' does not match"
        
        @staticmethod
        def assert_list_contains_dict(actual_list: List[Dict], expected_dict: Dict[str, Any]):
            """断言列表包含指定的字典"""
            for item in actual_list:
                if all(item.get(k) == v for k, v in expected_dict.items()):
                    return True
            assert False, f"Expected dict {expected_dict} not found in list"
        
        @staticmethod
        def create_test_file(path: str, content: str):
            """创建测试文件"""
            os.makedirs(os.path.dirname(path), exist_ok=True)
            with open(path, 'w', encoding='utf-8') as f:
                f.write(content)
        
        @staticmethod
        def read_test_file(path: str) -> str:
            """读取测试文件"""
            with open(path, 'r', encoding='utf-8') as f:
                return f.read()
        
        @staticmethod
        def assert_file_exists(path: str):
            """断言文件存在"""
            assert os.path.exists(path), f"File {path} does not exist"
        
        @staticmethod
        def assert_file_not_exists(path: str):
            """断言文件不存在"""
            assert not os.path.exists(path), f"File {path} should not exist"
        
        @staticmethod
        def assert_dataframe_equal(df1: pd.DataFrame, df2: pd.DataFrame):
            """断言DataFrame相等"""
            if pd is None:
                pytest.skip("pandas not available")
            pd.testing.assert_frame_equal(df1, df2)
        
        @staticmethod
        def assert_array_equal(arr1: np.ndarray, arr2: np.ndarray):
            """断言NumPy数组相等"""
            if np is None:
                pytest.skip("numpy not available")
            np.testing.assert_array_equal(arr1, arr2)
        
        @staticmethod
        def assert_array_almost_equal(arr1: np.ndarray, arr2: np.ndarray, decimal: int = 7):
            """断言NumPy数组近似相等"""
            if np is None:
                pytest.skip("numpy not available")
            np.testing.assert_array_almost_equal(arr1, arr2, decimal=decimal)
    
    return TestUtils()


# ============================================================================
# 标记定义
# ============================================================================

# 自定义标记
pytestmark = [
    pytest.mark.filterwarnings("ignore::DeprecationWarning"),
    pytest.mark.filterwarnings("ignore::PendingDeprecationWarning"),
    pytest.mark.filterwarnings("ignore::UserWarning")
]


# ============================================================================
# 测试钩子
# ============================================================================

def pytest_runtest_setup(item):
    """测试运行前设置"""
    # 设置测试特定的环境变量
    os.environ['TEST_NAME'] = item.name
    os.environ['TEST_MODULE'] = item.module.__name__


def pytest_runtest_teardown(item, nextitem):
    """测试运行后清理"""
    # 清理测试特定的环境变量
    os.environ.pop('TEST_NAME', None)
    os.environ.pop('TEST_MODULE', None)
    
    # 强制垃圾回收
    import gc
    gc.collect()


def pytest_exception_interact(node, call, report):
    """测试异常交互"""
    if report.failed:
        # 记录失败的测试信息
        logging.error(f"Test failed: {node.name}")
        logging.error(f"Exception: {call.excinfo.value}")


def pytest_terminal_summary(terminalreporter, exitstatus, config):
    """终端摘要报告"""
    if exitstatus == 0:
        terminalreporter.write_line("\n✅ All tests passed successfully!", green=True)
    else:
        terminalreporter.write_line("\n❌ Some tests failed!", red=True)
    
    # 显示测试统计
    stats = terminalreporter.stats
    if 'passed' in stats:
        passed = len(stats['passed'])
        terminalreporter.write_line(f"Passed: {passed}", green=True)
    
    if 'failed' in stats:
        failed = len(stats['failed'])
        terminalreporter.write_line(f"Failed: {failed}", red=True)
    
    if 'skipped' in stats:
        skipped = len(stats['skipped'])
        terminalreporter.write_line(f"Skipped: {skipped}", yellow=True)


# ============================================================================
# 插件配置
# ============================================================================

# 注册自定义标记已在pytest_configure中处理


# ============================================================================
# 全局变量和常量
# ============================================================================

# 测试数据目录
TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), 'test_data')

# 测试输出目录
TEST_OUTPUT_DIR = os.path.join(os.path.dirname(__file__), 'test_output')

# 创建测试目录
os.makedirs(TEST_DATA_DIR, exist_ok=True)
os.makedirs(TEST_OUTPUT_DIR, exist_ok=True)

# 测试配置
TEST_CONFIG = {
    'database_url': 'sqlite:///:memory:',
    'api_base_url': 'http://localhost:8000',
    'timeout': 30,
    'retry_count': 3,
    'debug': True
}